--[[--------------------------------------------------
abbrevlist.lua
Authors: Dmitry Maslov, frs, mozers? Tymur Gubayev
version 3.4.16
------------------------------------------------------
  If you insert the abbreviation (Ctrl + B) did not find an exact match,
  then the list of matches starting with this combination of characters.
  Provides automatic mode (the appearance of the list without pressing Ctrl + B).
  It includes parameters abbrev.lexer.auto = 3,
        where lexer - the name of the lexer
              a 3 - min length of the input string at which it will be analyzed as an abbreviation

  If the abbreviation has more than one cursor marks, when you insert the cursor is placed on deciphering the first one.
  All other marks are set invisible, moving on by pressing Tab.
  When set abbrev.multitab.clear.manual = 1 script does not clear label tabs on them after moving to Tab. User deletes them manually combination Ctrl + Tab.
  Parameter abbrev.multitab.indic.style = # FF6600, diagonal allows you to show tabs specified style tags (the values ​​are the same as in the parameters indic.style.number)
  Parameter setting abbrev.lexer.ignore.comment = 1 allows the script to ignore the comment character in the files for these abbreviations lexer (ie all commented lines are switched off with the initial abbreviation with #)
  Abbrev.list.width parameter can specify the maximum width of the drop-down list of abbreviations transcripts (in characters)
  Abbrev.lexer.prev.chars parameter can be set individually for favorite lexer list of characters other than the Default set '([{<', which precede the abbreviation. Note that these characters will not be possible to include in the acronyms!

  Warning:
  Built-in functions SciTE (Ctrl + B, Ctrl + Shift + R), which replaces the script works so much better!
  So files from the original cuts SciTE be careful revision.

  Connection:
    In SciTEStartup.lua file add the line:
    dofile (props["SciteDefaultHome"].."\\tools\\abbrevlist.lua")
--]]--------------------------------------------------

local table_abbr_exp = {}     -- a complete list of abbreviations and the transcripts to them
local table_user_list = {}    -- to the current list of approved abbreviations and text transcripts to them
local get_abbrev = true       -- indication that this list must recreate
local chars_count_min = 0     -- min length of the input string at which it will be analyzed
local sep = '\1'              -- delimiter line drop-down list
local typeUserList = 11       -- ID drop-down list
local smart_tab = 0           -- Number of extra tabs (invisible markers)
local cr = string.char(1)     -- symbol for temporary substitution tags cursor |
local clearmanual = tonumber(props['abbrev.multitab.clear.manual']) == 1
local abbrev_length = 0       -- length of abbreviations
local prev_chars = ' ([{<'    -- characters that precede the abbreviation

-- Returns the number of a free token and assigns it the attribute "invisible"
local function SetHiddenMarker()
	for indic_number = 0, 31 do
		local mark = props["indic.style."..indic_number]
		if mark == "" then
			local indic_style = props["abbrev.multitab.indic.style"]
			if indic_style == '' then
				props["indic.style."..indic_number] = "hidden"
			else
				props["indic.style."..indic_number] = indic_style
			end
			return indic_number
		end
	end
end
local num_hidden_indic = SetHiddenMarker()   -- marker number cursor positions (to bypass by TAB)

-- Read all connected abbrev-file into a table table_abbr_exp
local function CreateExpansionList()
	local abbrev_filename = props["AbbrevPath"]
	if abbrev_filename == '' then return end
	table_abbr_exp = ReadAbbrevFile(abbrev_filename) or {}
	for k,v in pairs(table_abbr_exp) do
		v.abbr = v.abbr:utf8upper()
		v.exp = v.exp:gsub('\t','\\t')
	end
end

-- Insert transcripts from the drop
local function InsertExpansion(expansion, abbrev_length)
	if not abbrev_length then abbrev_length = 0 end
	editor:BeginUndoAction()
	-- Clears the abbreviation maintaining separation
	local sel_start, sel_end = editor.SelectionStart - abbrev_length, editor.SelectionEnd - abbrev_length
	if abbrev_length > 0 then
		editor:remove(sel_start, editor.SelectionStart)
		editor:SetSel(sel_start, sel_end)
		abbrev_length = 0
	end
	-- Box decrypt c replacing all labels cursor | (except the first) to the symbol cr
	expansion = expansion:gsub("|", cr):gsub(cr..cr, "||"):gsub(cr, "|", 1)
	local _, tab_count = expansion:gsub(cr, cr) -- determine the number of additional labels cursor
	local before_length = editor.Length
	scite.InsertAbbreviation(expansion)
	--------------------------------------------------
	if tab_count>0 then -- if there are additional tags cursor
		local start_pos = editor.CurrentPos
		local end_pos = sel_end + editor.Length - before_length
		if clearmanual then
			EditorMarkText(start_pos-1, 1, num_hidden_indic)
		else
			EditorClearMarks(num_hidden_indic) -- If the previous markers were inserted (the user does not fill in all fields), then delete them
		end

		repeat -- remove the # from the transcripts, replacing them invisible markers
			local tab_start = editor:findtext(cr, 0, end_pos, start_pos)
			if not tab_start then break end
			editor:GotoPos(tab_start+1)  editor:DeleteBack()
			EditorMarkText(tab_start-1, 1, num_hidden_indic)
			end_pos = tab_start-1
		until false

		editor:GotoPos(start_pos)
		smart_tab = tab_count -- allow special handling pressing TAB (Event OnKey)
	end
	--------------------------------------------------
	editor:EndUndoAction()
end
-- export global
scite_InsertAbbreviation = InsertExpansion

-- Display the list of transcripts that match the abbreviation
local function ShowExpansionList(event_IDM_ABBREV)
	if get_abbrev then -- when opening and switching tabs
		-- Specifies the character that precede the abbreviation
		prev_chars = props['abbrev.'..props['Language']..'.prev.chars']
		if prev_chars == '' then prev_chars = ' ([{<' end
		prev_chars = '['..prev_chars:gsub(' ', 's'):gsub('(.)', '\\%1')..']'
	end
	local sel_start = editor.SelectionStart
	local line_start_pos = editor:PositionFromLine(editor:LineFromPosition(sel_start))
	-- start looking for cuts - the first space character
	local abbrev_start = editor:findtext(prev_chars, SCFIND_REGEXP, sel_start-1, line_start_pos) --@ `-1` in `sel_start-1` is propably wrong, but so you can make "& =bla" abbreviation
	abbrev_start = abbrev_start and abbrev_start+1 or line_start_pos

	local abbrev = editor:textrange(abbrev_start, sel_start)
	abbrev_length = #abbrev
	if abbrev_length == 0 then return event_IDM_ABBREV end
	-- if the length is less likely abbreviations given number of characters then exit
	if not event_IDM_ABBREV and abbrev_length < chars_count_min then return true end

	-- If we switched to a different file, then re-creates the table table_abbr_exp
	if get_abbrev then
		CreateExpansionList()
		get_abbrev = false
	end
	if #table_abbr_exp == 0 then return event_IDM_ABBREV end

	local cp = editor:codepage()
	if cp ~= 65001 then abbrev = abbrev:to_utf8(cp) end
	abbrev = abbrev:utf8upper()
	table_user_list = {}
	 -- select only the records of table_abbr_exp appropriate the acronym
	for i = 1, #table_abbr_exp do
		if table_abbr_exp[i].abbr:find(abbrev, 1, true) == 1 then
			table_user_list[#table_user_list+1] = {table_abbr_exp[i].abbr, table_abbr_exp[i].exp}
		end
	end
	if #table_user_list == 0 then return event_IDM_ABBREV end
	-- if we use the Ctrl + B (not automatic activation)
	if (event_IDM_ABBREV)
		-- and if it is found only option decryption
		and (#table_user_list == 1)
		-- and is fully consistent with the abbreviation entered
		and (abbrev == table_user_list[1][1])
			-- then insert immediately produce
			then
				InsertExpansion(table_user_list[1][2], abbrev_length)
				return true
	end

	-- show a drop-down list of transcripts that match the abbreviation
	local tmp = {}
	local list_width = tonumber(props['abbrev.list.width']) or -1
	for i = 1, #table_user_list do
		tmp[#tmp+1] = table_user_list[i][2]:sub(1, list_width)
	end
	local table_user_list_string = table.concat(tmp, sep):gsub('%?', ' ')
	local sep_tmp = editor.AutoCSeparator
	editor.AutoCSeparator = string.byte(sep)
	editor:UserListShow(typeUserList, table_user_list_string)
	editor.AutoCSeparator = sep_tmp
	return true
end

------------------------------------------------------
AddEventHandler("OnMenuCommand", function(msg)
	if msg == IDM_ABBREV then
		return ShowExpansionList(true)
	end
end)

AddEventHandler("OnChar", function()
	chars_count_min = tonumber(props['abbrev.'..props['Language']..'.auto']) or tonumber(props['abbrev.*.auto']) or 0
	if chars_count_min ~= 0 then
		return ShowExpansionList(false)
	end
end)

AddEventHandler("OnKey", function(key, shift, ctrl, alt)
	if editor.Focus and smart_tab > 0 and key == 9 then -- TAB=9
		if not (shift or ctrl or alt) then
			for i = editor.CurrentPos, editor.Length do
				if editor:IndicatorValueAt(num_hidden_indic, i)==1 then
					editor:GotoPos(i+1)
					if not clearmanual then
						EditorClearMarks(num_hidden_indic, i, 1) -- after the transition to the position of a given marker, remove the marker
						smart_tab = smart_tab - 1
					end
					return true
				end
			end
		elseif ctrl and not (shift or alt) then
			EditorClearMarks(num_hidden_indic)
			smart_tab = 0
			return true
		end
	end
end)

AddEventHandler("OnUserListSelection", function(tp, sel_value, sel_item_id)
	if tp == typeUserList then
		InsertExpansion(table_user_list[sel_item_id][2], abbrev_length)
	end
end)

AddEventHandler("OnSwitchFile", function()
	get_abbrev = true
end)

AddEventHandler("OnOpen", function()
	get_abbrev = true
end)
